/**
 * 
 */
package com.patternity.model;

import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import com.patternity.model.Motif.DeclarationMode;
import com.patternity.model.Motif.Participant;

/**
 * Given an input model, design declaration and design motif, generates a
 * specific model for display purposes.
 * 
 * @author Cyrille.Martraire
 */
public class MotifWeaver {

	private final ModelBuilder displayModel = new SimpleModel();

	private final Map<String, Set<Element>> byRole = new HashMap<String, Set<Element>>();

	/**
	 * Weaves the given model, the declaration and the model template together
	 * into a new model
	 * 
	 * @param givenModel
	 *            The actual given model, extracted from the code base
	 * @param template
	 *            The pre-canned model template for the same design intent
	 * @param dd
	 *            The declaration of the design intent done in the code
	 * @return The weaved model
	 */
	public ModelBuilder generate(final Model givenModel, final Pattern pattern,
			final DesignDeclaration dd) {
		return generate(givenModel, pattern, displayModel, dd);
	}

	private void put(final String role, final Element element) {
		Set<Element> list = byRole.get(role);
		if (list == null) {
			list = new HashSet<Element>();
			byRole.put(role, list);
		}
		list.add(element);
	}

	private Element get(final String role) {
		final Set<Element> elements = getAll(role);
		return elements == null || elements.isEmpty() ? null : elements
				.iterator().next();
	}

	private Set<Element> getAll(final String role) {
		final Set<Element> list = byRole.get(role);
		return list == null ? Collections.<Element> emptySet() : list;
	}

	/**
	 * Weaves the given model, the declaration and the model template together
	 * into a new model
	 * 
	 * @param givenModel
	 *            The actual given model, extracted from the code base
	 * @param template
	 *            The pre-canned model template for the same design intent
	 * @param dd
	 *            The declaration of the design intent done in the code
	 * @return The weaved model
	 */
	public ModelBuilder generate(final Model givenModel, final Pattern pattern,
			final ModelBuilder resultModel, final DesignDeclaration dd) {
		// The type that hosts the design declaration
		final Element mainType = givenModel.getElement(dd.getSite());
		final Element displayMainType = displayModel
				.addElementIfNotThere(new Element(mainType.getName()));

		// resolve the role of the declaration site
		final Motif motif = pattern.getMotif();
		final Participant mainParticipant = motif.getDeclaredParticipant();
		if (mainParticipant != null) {
			final String mainRoleName = mainParticipant.getName();
			displayMainType.addSterotype(mainRoleName);
			put(mainParticipant.getName(), displayMainType);
		}
		if (pattern.isStereotype()) {
			displayMainType.addSterotype(pattern.getName());
		}
		for (final String implied : pattern.getImpliedPatterns()) {
			displayMainType.addSterotype(implied);
		}
		if (pattern.isStereotype()) {
			return displayModel;
		}

		// referenced roles
		for (final Participant participant : motif.getParticipants()) {
			final DeclarationMode mode = participant.getMode();
			if (mode.equals(DeclarationMode.DECLARED_BY_ATTRIBUTE_NAMED)) {
				final String key = participant.getDeclarationKey();
				// TODO support multivalues
				final String value = (String) dd.getValue(key);

				// No value for an interface means self, e.g. composite is also
				// the component
				final boolean isSelfReference = value == null;
				Element element = displayMainType;
				if (!isSelfReference) {
					final Element referencedElement = givenModel
							.getElement(value);
					element = displayModel.addElementIfNotThere(new Element(
							referencedElement.getName()));
					for (final String stereotype : referencedElement
							.getStereotypes()) {
						element.addSterotype(stereotype);
					}
				}
				// resolve the actual role within the pattern
				element.addSterotype(participant.getName());
				put(participant.getName(), element);
			}
		}

		// referenced roles
		for (final Participant participant : motif.getParticipants()) {
			final DeclarationMode mode = participant.getMode();
			if (mode.equals(DeclarationMode.INFERRED_AS_SUBTYPE_OF_PARTICIPANT)) {
				final String superRole = participant.getDeclarationKey();
				final Element superType = get(superRole);
				// implied sub types
				final List<Element> subtypes = givenModel
						.getSubTypes(superType);
				for (final Element subtype : subtypes) {
					final Element element = displayModel
							.addElementIfNotThere(new Element(subtype.getName()));
					element.addSterotype(participant.getName());
					put(participant.getName(), element);// TODO multimap
				}
			}
		}

		for (final Relation r : motif.getRelations()) {
			foreachRelation(r);
		}
		return displayModel;
	}

	// DONE N-1
	// TODO mapping from-to: 1-1, 1-N, N-1
	// TODO N-N with explicit mapping criteria
	private void foreachRelation(final Relation r) {
		final String name = r.getName();
		final Set<Element> allFrom = getAll((String) r.getFrom());
		for (final Element from : allFrom) {
			final Element to = get((String) r.getTo());
			if (to == null) {
				return;
			}
			final Relation relation = displayModel.addRelation(new Relation(
					name, from, to));
			for (final String stereotype : r.getStereotypes()) {
				relation.addStereotype(stereotype);
			}

		}
	}
}
